home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / lib / ruby / 1.8 / benchmark.rb < prev    next >
Text File  |  2008-02-10  |  18KB  |  573 lines

  1. =begin
  2. #
  3. # benchmark.rb - a performance benchmarking library 
  4. # $Id: benchmark.rb 15425 2008-02-10 15:24:56Z naruse $
  5. # Created by Gotoken (gotoken@notwork.org). 
  6. #
  7. # Documentation by Gotoken (original RD), Lyle Johnson (RDoc conversion), and
  8. # Gavin Sinclair (editing). 
  9. #
  10. =end
  11.  
  12. # == Overview
  13. #
  14. # The Benchmark module provides methods for benchmarking Ruby code, giving
  15. # detailed reports on the time taken for each task.
  16. #
  17.  
  18. # The Benchmark module provides methods to measure and report the time
  19. # used to execute Ruby code.
  20. #
  21. # * Measure the time to construct the string given by the expression
  22. #   <tt>"a"*1_000_000</tt>:
  23. #
  24. #       require 'benchmark'
  25. #
  26. #       puts Benchmark.measure { "a"*1_000_000 }
  27. #   On my machine (FreeBSD 3.2 on P5, 100MHz) this generates:
  28. #       1.166667   0.050000   1.216667 (  0.571355)
  29. #   This report shows the user CPU time, system CPU time, the sum of
  30. #   the user and system CPU times, and the elapsed real time. The unit
  31. #   of time is seconds.
  32. # * Do some experiments sequentially using the #bm method:
  33. #
  34. #       require 'benchmark'
  35. #
  36. #       n = 50000
  37. #       Benchmark.bm do |x|
  38. #         x.report { for i in 1..n; a = "1"; end }
  39. #         x.report { n.times do   ; a = "1"; end }
  40. #         x.report { 1.upto(n) do ; a = "1"; end }
  41. #       end
  42. #   The result:
  43. #
  44. #              user     system      total        real
  45. #          1.033333   0.016667   1.016667 (  0.492106)
  46. #          1.483333   0.000000   1.483333 (  0.694605)
  47. #          1.516667   0.000000   1.516667 (  0.711077)
  48. #
  49. # * Continuing the previous example, put a label in each report:
  50. #
  51. #       require 'benchmark'
  52. #
  53. #       n = 50000
  54. #       Benchmark.bm(7) do |x|
  55. #         x.report("for:")   { for i in 1..n; a = "1"; end }
  56. #         x.report("times:") { n.times do   ; a = "1"; end }
  57. #         x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
  58. #       end
  59. # The result:
  60. #                     user     system      total        real
  61. #        for:     1.050000   0.000000   1.050000 (  0.503462)
  62. #        times:   1.533333   0.016667   1.550000 (  0.735473)
  63. #        upto:    1.500000   0.016667   1.516667 (  0.711239)
  64. #
  65. # * The times for some benchmarks depend on the order in which items
  66. #   are run.  These differences are due to the cost of memory
  67. #   allocation and garbage collection. To avoid these discrepancies,
  68. #   the #bmbm method is provided.  For example, to compare ways to
  69. #   sort an array of floats:
  70. #
  71. #       require 'benchmark'
  72. #       
  73. #       array = (1..1000000).map { rand }
  74. #       
  75. #       Benchmark.bmbm do |x|
  76. #         x.report("sort!") { array.dup.sort! }
  77. #         x.report("sort")  { array.dup.sort  }
  78. #       end
  79. #   The result:
  80. #        Rehearsal -----------------------------------------
  81. #        sort!  11.928000   0.010000  11.938000 ( 12.756000)
  82. #        sort   13.048000   0.020000  13.068000 ( 13.857000)
  83. #        ------------------------------- total: 25.006000sec
  84. #        
  85. #                    user     system      total        real
  86. #        sort!  12.959000   0.010000  12.969000 ( 13.793000)
  87. #        sort   12.007000   0.000000  12.007000 ( 12.791000)
  88. #
  89. #
  90. # * Report statistics of sequential experiments with unique labels,
  91. #   using the #benchmark method:
  92. #
  93. #       require 'benchmark'
  94. #
  95. #       n = 50000
  96. #       Benchmark.benchmark(" "*7 + CAPTION, 7, FMTSTR, ">total:", ">avg:") do |x|
  97. #         tf = x.report("for:")   { for i in 1..n; a = "1"; end }
  98. #         tt = x.report("times:") { n.times do   ; a = "1"; end }
  99. #         tu = x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
  100. #         [tf+tt+tu, (tf+tt+tu)/3]
  101. #       end
  102. #   The result:
  103. #                     user     system      total        real
  104. #        for:     1.016667   0.016667   1.033333 (  0.485749)
  105. #        times:   1.450000   0.016667   1.466667 (  0.681367)
  106. #        upto:    1.533333   0.000000   1.533333 (  0.722166)
  107. #        >total:  4.000000   0.033333   4.033333 (  1.889282)
  108. #        >avg:    1.333333   0.011111   1.344444 (  0.629761)
  109.  
  110. module Benchmark
  111.  
  112.   BENCHMARK_VERSION = "2002-04-25" #:nodoc"
  113.  
  114.   def Benchmark::times() # :nodoc:
  115.       Process::times()
  116.   end
  117.  
  118.  
  119.   # Invokes the block with a <tt>Benchmark::Report</tt> object, which
  120.   # may be used to collect and report on the results of individual
  121.   # benchmark tests. Reserves <i>label_width</i> leading spaces for
  122.   # labels on each line. Prints _caption_ at the top of the
  123.   # report, and uses _fmt_ to format each line.
  124.   # If the block returns an array of
  125.   # <tt>Benchmark::Tms</tt> objects, these will be used to format
  126.   # additional lines of output. If _label_ parameters are
  127.   # given, these are used to label these extra lines.
  128.   #
  129.   # _Note_: Other methods provide a simpler interface to this one, and are
  130.   # suitable for nearly all benchmarking requirements.  See the examples in
  131.   # Benchmark, and the #bm and #bmbm methods.
  132.   #
  133.   # Example: 
  134.   #
  135.   #     require 'benchmark'
  136.   #     include Benchmark          # we need the CAPTION and FMTSTR constants 
  137.   #
  138.   #     n = 50000
  139.   #     Benchmark.benchmark(" "*7 + CAPTION, 7, FMTSTR, ">total:", ">avg:") do |x|
  140.   #       tf = x.report("for:")   { for i in 1..n; a = "1"; end }
  141.   #       tt = x.report("times:") { n.times do   ; a = "1"; end }
  142.   #       tu = x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
  143.   #       [tf+tt+tu, (tf+tt+tu)/3]
  144.   #     end
  145.   # 
  146.   # <i>Generates:</i>
  147.   # 
  148.   #                     user     system      total        real
  149.   #        for:     1.016667   0.016667   1.033333 (  0.485749)
  150.   #        times:   1.450000   0.016667   1.466667 (  0.681367)
  151.   #        upto:    1.533333   0.000000   1.533333 (  0.722166)
  152.   #        >total:  4.000000   0.033333   4.033333 (  1.889282)
  153.   #        >avg:    1.333333   0.011111   1.344444 (  0.629761)
  154.   # 
  155.  
  156.   def benchmark(caption = "", label_width = nil, fmtstr = nil, *labels) # :yield: report
  157.     sync = STDOUT.sync
  158.     STDOUT.sync = true
  159.     label_width ||= 0
  160.     fmtstr ||= FMTSTR
  161.     raise ArgumentError, "no block" unless iterator?
  162.     print caption
  163.     results = yield(Report.new(label_width, fmtstr))
  164.     Array === results and results.grep(Tms).each {|t|
  165.       print((labels.shift || t.label || "").ljust(label_width), 
  166.             t.format(fmtstr))
  167.     }
  168.     STDOUT.sync = sync
  169.   end
  170.  
  171.  
  172.   # A simple interface to the #benchmark method, #bm is generates sequential reports
  173.   # with labels.  The parameters have the same meaning as for #benchmark.
  174.   # 
  175.   #     require 'benchmark'
  176.   #
  177.   #     n = 50000
  178.   #     Benchmark.bm(7) do |x|
  179.   #       x.report("for:")   { for i in 1..n; a = "1"; end }
  180.   #       x.report("times:") { n.times do   ; a = "1"; end }
  181.   #       x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
  182.   #     end
  183.   # 
  184.   # <i>Generates:</i>
  185.   # 
  186.   #                     user     system      total        real
  187.   #        for:     1.050000   0.000000   1.050000 (  0.503462)
  188.   #        times:   1.533333   0.016667   1.550000 (  0.735473)
  189.   #        upto:    1.500000   0.016667   1.516667 (  0.711239)
  190.   #
  191.  
  192.   def bm(label_width = 0, *labels, &blk) # :yield: report
  193.     benchmark(" "*label_width + CAPTION, label_width, FMTSTR, *labels, &blk)
  194.   end
  195.  
  196.  
  197.   # Sometimes benchmark results are skewed because code executed
  198.   # earlier encounters different garbage collection overheads than
  199.   # that run later. #bmbm attempts to minimize this effect by running
  200.   # the tests twice, the first time as a rehearsal in order to get the
  201.   # runtime environment stable, the second time for
  202.   # real. <tt>GC.start</tt> is executed before the start of each of
  203.   # the real timings; the cost of this is not included in the
  204.   # timings. In reality, though, there's only so much that #bmbm can
  205.   # do, and the results are not guaranteed to be isolated from garbage
  206.   # collection and other effects.
  207.   #
  208.   # Because #bmbm takes two passes through the tests, it can
  209.   # calculate the required label width.
  210.   #
  211.   #       require 'benchmark'
  212.   #       
  213.   #       array = (1..1000000).map { rand }
  214.   #       
  215.   #       Benchmark.bmbm do |x|
  216.   #         x.report("sort!") { array.dup.sort! }
  217.   #         x.report("sort")  { array.dup.sort  }
  218.   #       end
  219.   # 
  220.   # <i>Generates:</i>
  221.   # 
  222.   #        Rehearsal -----------------------------------------
  223.   #        sort!  11.928000   0.010000  11.938000 ( 12.756000)
  224.   #        sort   13.048000   0.020000  13.068000 ( 13.857000)
  225.   #        ------------------------------- total: 25.006000sec
  226.   #        
  227.   #                    user     system      total        real
  228.   #        sort!  12.959000   0.010000  12.969000 ( 13.793000)
  229.   #        sort   12.007000   0.000000  12.007000 ( 12.791000)
  230.   #
  231.   # #bmbm yields a Benchmark::Job object and returns an array of
  232.   # Benchmark::Tms objects.
  233.   #
  234.   def bmbm(width = 0, &blk) # :yield: job
  235.     job = Job.new(width)
  236.     yield(job)
  237.     width = job.width
  238.     sync = STDOUT.sync
  239.     STDOUT.sync = true
  240.  
  241.     # rehearsal
  242.     print "Rehearsal "
  243.     puts '-'*(width+CAPTION.length - "Rehearsal ".length)
  244.     list = []
  245.     job.list.each{|label,item|
  246.       print(label.ljust(width))
  247.       res = Benchmark::measure(&item)
  248.       print res.format()
  249.       list.push res
  250.     }
  251.     sum = Tms.new; list.each{|i| sum += i}
  252.     ets = sum.format("total: %tsec")
  253.     printf("%s %s\n\n",
  254.            "-"*(width+CAPTION.length-ets.length-1), ets)
  255.     
  256.     # take
  257.     print ' '*width, CAPTION
  258.     list = []
  259.     ary = []
  260.     job.list.each{|label,item|
  261.       GC::start
  262.       print label.ljust(width)
  263.       res = Benchmark::measure(&item)
  264.       print res.format()
  265.       ary.push res
  266.       list.push [label, res]
  267.     }
  268.  
  269.     STDOUT.sync = sync
  270.     ary
  271.   end
  272.  
  273.   # 
  274.   # Returns the time used to execute the given block as a
  275.   # Benchmark::Tms object.
  276.   #
  277.   def measure(label = "") # :yield:
  278.     t0, r0 = Benchmark.times, Time.now
  279.     yield
  280.     t1, r1 = Benchmark.times, Time.now
  281.     Benchmark::Tms.new(t1.utime  - t0.utime, 
  282.                        t1.stime  - t0.stime, 
  283.                        t1.cutime - t0.cutime, 
  284.                        t1.cstime - t0.cstime, 
  285.                        r1.to_f - r0.to_f,
  286.                        label)
  287.   end
  288.  
  289.   #
  290.   # Returns the elapsed real time used to execute the given block.
  291.   #
  292.   def realtime(&blk) # :yield:
  293.     r0 = Time.now
  294.     yield
  295.     r1 = Time.now
  296.     r1.to_f - r0.to_f
  297.   end
  298.  
  299.  
  300.  
  301.   #
  302.   # A Job is a sequence of labelled blocks to be processed by the
  303.   # Benchmark.bmbm method.  It is of little direct interest to the user.
  304.   #
  305.   class Job # :nodoc:
  306.     #
  307.     # Returns an initialized Job instance.
  308.     # Usually, one doesn't call this method directly, as new
  309.     # Job objects are created by the #bmbm method.
  310.     # _width_ is a initial value for the label offset used in formatting;
  311.     # the #bmbm method passes its _width_ argument to this constructor. 
  312.     # 
  313.     def initialize(width)
  314.       @width = width
  315.       @list = []
  316.     end
  317.  
  318.     #
  319.     # Registers the given label and block pair in the job list.
  320.     #
  321.     def item(label = "", &blk) # :yield:
  322.       raise ArgumentError, "no block" unless block_given?
  323.       label += ' '
  324.       w = label.length
  325.       @width = w if @width < w
  326.       @list.push [label, blk]
  327.       self
  328.     end
  329.  
  330.     alias report item
  331.     
  332.     # An array of 2-element arrays, consisting of label and block pairs.
  333.     attr_reader :list
  334.     
  335.     # Length of the widest label in the #list, plus one.  
  336.     attr_reader :width
  337.   end
  338.  
  339.   module_function :benchmark, :measure, :realtime, :bm, :bmbm
  340.  
  341.  
  342.  
  343.   #
  344.   # This class is used by the Benchmark.benchmark and Benchmark.bm methods.
  345.   # It is of little direct interest to the user.
  346.   #
  347.   class Report # :nodoc:
  348.     #
  349.     # Returns an initialized Report instance.
  350.     # Usually, one doesn't call this method directly, as new
  351.     # Report objects are created by the #benchmark and #bm methods. 
  352.     # _width_ and _fmtstr_ are the label offset and 
  353.     # format string used by Tms#format. 
  354.     # 
  355.     def initialize(width = 0, fmtstr = nil)
  356.       @width, @fmtstr = width, fmtstr
  357.     end
  358.  
  359.     #
  360.     # Prints the _label_ and measured time for the block,
  361.     # formatted by _fmt_. See Tms#format for the
  362.     # formatting rules.
  363.     #
  364.     def item(label = "", *fmt, &blk) # :yield:
  365.       print label.ljust(@width)
  366.       res = Benchmark::measure(&blk)
  367.       print res.format(@fmtstr, *fmt)
  368.       res
  369.     end
  370.  
  371.     alias report item
  372.   end
  373.  
  374.  
  375.  
  376.   #
  377.   # A data object, representing the times associated with a benchmark
  378.   # measurement.
  379.   #
  380.   class Tms
  381.     CAPTION = "      user     system      total        real\n"
  382.     FMTSTR = "%10.6u %10.6y %10.6t %10.6r\n"
  383.  
  384.     # User CPU time
  385.     attr_reader :utime
  386.     
  387.     # System CPU time
  388.     attr_reader :stime
  389.    
  390.     # User CPU time of children
  391.     attr_reader :cutime
  392.     
  393.     # System CPU time of children
  394.     attr_reader :cstime
  395.     
  396.     # Elapsed real time
  397.     attr_reader :real
  398.     
  399.     # Total time, that is _utime_ + _stime_ + _cutime_ + _cstime_ 
  400.     attr_reader :total
  401.     
  402.     # Label
  403.     attr_reader :label
  404.  
  405.     #
  406.     # Returns an initialized Tms object which has
  407.     # _u_ as the user CPU time, _s_ as the system CPU time, 
  408.     # _cu_ as the children's user CPU time, _cs_ as the children's
  409.     # system CPU time, _real_ as the elapsed real time and _l_
  410.     # as the label. 
  411.     # 
  412.     def initialize(u = 0.0, s = 0.0, cu = 0.0, cs = 0.0, real = 0.0, l = nil)
  413.       @utime, @stime, @cutime, @cstime, @real, @label = u, s, cu, cs, real, l
  414.       @total = @utime + @stime + @cutime + @cstime
  415.     end
  416.  
  417.     # 
  418.     # Returns a new Tms object whose times are the sum of the times for this
  419.     # Tms object, plus the time required to execute the code block (_blk_).
  420.     # 
  421.     def add(&blk) # :yield:
  422.       self + Benchmark::measure(&blk) 
  423.     end
  424.  
  425.     # 
  426.     # An in-place version of #add.
  427.     # 
  428.     def add!
  429.       t = Benchmark::measure(&blk) 
  430.       @utime  = utime + t.utime
  431.       @stime  = stime + t.stime
  432.       @cutime = cutime + t.cutime
  433.       @cstime = cstime + t.cstime
  434.       @real   = real + t.real
  435.       self
  436.     end
  437.  
  438.     # 
  439.     # Returns a new Tms object obtained by memberwise summation
  440.     # of the individual times for this Tms object with those of the other
  441.     # Tms object.
  442.     # This method and #/() are useful for taking statistics. 
  443.     # 
  444.     def +(other); memberwise(:+, other) end
  445.     
  446.     #
  447.     # Returns a new Tms object obtained by memberwise subtraction
  448.     # of the individual times for the other Tms object from those of this
  449.     # Tms object.
  450.     #
  451.     def -(other); memberwise(:-, other) end
  452.     
  453.     #
  454.     # Returns a new Tms object obtained by memberwise multiplication
  455.     # of the individual times for this Tms object by _x_.
  456.     #
  457.     def *(x); memberwise(:*, x) end
  458.  
  459.     # 
  460.     # Returns a new Tms object obtained by memberwise division
  461.     # of the individual times for this Tms object by _x_.
  462.     # This method and #+() are useful for taking statistics. 
  463.     # 
  464.     def /(x); memberwise(:/, x) end
  465.  
  466.     #
  467.     # Returns the contents of this Tms object as
  468.     # a formatted string, according to a format string
  469.     # like that passed to Kernel.format. In addition, #format
  470.     # accepts the following extensions:
  471.     #
  472.     # <tt>%u</tt>::     Replaced by the user CPU time, as reported by Tms#utime.
  473.     # <tt>%y</tt>::     Replaced by the system CPU time, as reported by #stime (Mnemonic: y of "s*y*stem")
  474.     # <tt>%U</tt>::     Replaced by the children's user CPU time, as reported by Tms#cutime 
  475.     # <tt>%Y</tt>::     Replaced by the children's system CPU time, as reported by Tms#cstime
  476.     # <tt>%t</tt>::     Replaced by the total CPU time, as reported by Tms#total
  477.     # <tt>%r</tt>::     Replaced by the elapsed real time, as reported by Tms#real
  478.     # <tt>%n</tt>::     Replaced by the label string, as reported by Tms#label (Mnemonic: n of "*n*ame")
  479.     # 
  480.     # If _fmtstr_ is not given, FMTSTR is used as default value, detailing the
  481.     # user, system and real elapsed time.
  482.     # 
  483.     def format(arg0 = nil, *args)
  484.       fmtstr = (arg0 || FMTSTR).dup
  485.       fmtstr.gsub!(/(%[-+\.\d]*)n/){"#{$1}s" % label}
  486.       fmtstr.gsub!(/(%[-+\.\d]*)u/){"#{$1}f" % utime}
  487.       fmtstr.gsub!(/(%[-+\.\d]*)y/){"#{$1}f" % stime}
  488.       fmtstr.gsub!(/(%[-+\.\d]*)U/){"#{$1}f" % cutime}
  489.       fmtstr.gsub!(/(%[-+\.\d]*)Y/){"#{$1}f" % cstime}
  490.       fmtstr.gsub!(/(%[-+\.\d]*)t/){"#{$1}f" % total}
  491.       fmtstr.gsub!(/(%[-+\.\d]*)r/){"(#{$1}f)" % real}
  492.       arg0 ? Kernel::format(fmtstr, *args) : fmtstr
  493.     end
  494.  
  495.     # 
  496.     # Same as #format.
  497.     # 
  498.     def to_s
  499.       format
  500.     end
  501.  
  502.     # 
  503.     # Returns a new 6-element array, consisting of the
  504.     # label, user CPU time, system CPU time, children's
  505.     # user CPU time, children's system CPU time and elapsed
  506.     # real time.
  507.     # 
  508.     def to_a
  509.       [@label, @utime, @stime, @cutime, @cstime, @real]
  510.     end
  511.  
  512.     protected
  513.     def memberwise(op, x)
  514.       case x
  515.       when Benchmark::Tms
  516.         Benchmark::Tms.new(utime.__send__(op, x.utime),
  517.                            stime.__send__(op, x.stime),
  518.                            cutime.__send__(op, x.cutime),
  519.                            cstime.__send__(op, x.cstime),
  520.                            real.__send__(op, x.real)
  521.                            )
  522.       else
  523.         Benchmark::Tms.new(utime.__send__(op, x),
  524.                            stime.__send__(op, x),
  525.                            cutime.__send__(op, x),
  526.                            cstime.__send__(op, x),
  527.                            real.__send__(op, x)
  528.                            )
  529.       end
  530.     end
  531.   end
  532.  
  533.   # The default caption string (heading above the output times).
  534.   CAPTION = Benchmark::Tms::CAPTION
  535.  
  536.   # The default format string used to display times.  See also Benchmark::Tms#format. 
  537.   FMTSTR = Benchmark::Tms::FMTSTR
  538. end
  539.  
  540. if __FILE__ == $0
  541.   include Benchmark
  542.  
  543.   n = ARGV[0].to_i.nonzero? || 50000
  544.   puts %Q([#{n} times iterations of `a = "1"'])
  545.   benchmark("       " + CAPTION, 7, FMTSTR) do |x|
  546.     x.report("for:")   {for i in 1..n; a = "1"; end} # Benchmark::measure
  547.     x.report("times:") {n.times do   ; a = "1"; end}
  548.     x.report("upto:")  {1.upto(n) do ; a = "1"; end}
  549.   end
  550.  
  551.   benchmark do
  552.     [
  553.       measure{for i in 1..n; a = "1"; end},  # Benchmark::measure
  554.       measure{n.times do   ; a = "1"; end},
  555.       measure{1.upto(n) do ; a = "1"; end}
  556.     ]
  557.   end
  558. end
  559.